#include "ftpSocket.h"
#include <openssl/ssl.h>
#include <openssl/err.h>

#include <stdio_ext.h>


SOCKET creeConnectSocketClient(const char *nom_serveur, const char* port)
{
    // structure pour faire la demande
    struct addrinfo hints;
    // structure pour stocker et lire les résultats
    struct addrinfo *result, *rp;

    // variables pour tester si les fonctions donnent un résultats ou une erreur
    int res;
    int bon;
    // Des variable pour contenir de adresse de machine et des numero de port afin de les afficher
    char hname[NI_MAXHOST], sname[NI_MAXSERV];
    // la socket utilisée
    SOCKET s=-1;

    // on rempli la structure hints de demande d'adresse
    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_family = AF_UNSPEC;    /* IPv4 ou IPv6 */
    hints.ai_socktype = SOCK_STREAM; /* socket flux connectée */

    res = getaddrinfo(nom_serveur, port, &hints, &result);
    if (res != 0)   // c'est une erreur
    {
        fprintf(stderr, "ERR: getaddrinfo: %s\n", gai_strerror(res));
        exit(1);
    }

    // si res = 0 le véritable résultat de la fontion est l'argument result
    // qui contient une liste d'addresse correspondant à la demande on va les
    // rester jusqu'a trouver une qui convient
    rp = result;
    bon = 0;

    while (rp != NULL)
    {
        // on parcourt la liste pour en trouver une qui convienne

        // on récupère des informations affichables
        res = getnameinfo(rp->ai_addr, rp->ai_addrlen,
                          hname, NI_MAXHOST,
                          sname, NI_MAXSERV,
                          NI_NUMERICSERV|NI_NUMERICHOST);
        if (res != 0)
        {
            fprintf(stderr, "ERR: getnameinfo: %s\n", gai_strerror(res));
            exit (1);
        }

        // on essaye
        s = socket(rp->ai_family, rp->ai_socktype,rp->ai_protocol);
        // si le résultat est -1 cela n'a pas fonctionné on recommence avec la prochaine
        if (s == -1)
        {
            perror("Création de la socket");
            rp = rp->ai_next;
            continue;
        }

        // si la socket a été obtenue, on essaye de se connecter
        res = connect(s, rp->ai_addr, rp->ai_addrlen);
        if (res == 0 )  // cela a fonctionné on est connecté
        {
            bon = 1;
            fprintf(stderr, "OK: La connexion a été établie sur %s : %s\n",
                    hname, sname);
            break;
        }
        else   // sinon le bind a été impossible, il faut fermer la socket
        {
            perror("ERR: Impossible de se connecter");
            close (s);
        }

        rp = rp->ai_next;
    }

    if (bon == 0)   // Cela n'a jamais fonctionné
    {
        fprintf(stderr, "Aucune connexion possible\n");
        exit(1);
    }



    freeaddrinfo(result);           /* No longer needed */

    return s;
}

char* recoieMessage(int fd, void* s, bool ssl)
{
    char buff[TAILLE_BUFF];

    int taille_message = sizeof(char)* TAILLE_BUFF;

    char * messageTmp;

    char * message;
    message = malloc(taille_message);
    int res, res2;
    int nb_recu = 0;
    int i = 0;

    int fin = FALSE;

    while (fin == FALSE)
    {
        if(ssl)
        {
            res = SSL_read((SSL*)s, buff, TAILLE_BUFF);
            if (res < 0)
            {
                perror("Probleme Ã  la lecture du fichier");
                return "";
            }
            if (res == 0)
            {
                SSL_shutdown((SSL*)s);
                break;
            }
        }
        else
        {
            res = recv(*(SOCKET*)s, buff, TAILLE_BUFF, 0);
            if (res < 0)
            {
                perror("Probleme Ã  la lecture du fichier");
                return "Probleme Ã  la lecture du fichier";
            }
            if (res == 0)
            {
                close(*(SOCKET*)s);
                break;
            }
        }

        nb_recu += res;
        //    fprintf(stderr, "Recu %d oct total %d oct\n", res, nb_recu);
        res2 = write(fd, buff, res);
        if (res != res2)
        {
            perror("Probleme Ã  l'ecriture du fichier");
            return "";
        }

        if(buff[0] >= 48 && buff[0] <= 57 && buff[3] == ' ') //Si c'est un chiffre
        {
            //Ce if, c'est pour le début d'une réponse
            fin = TRUE;
        }

        if(nb_recu >= taille_message)
        {
            messageTmp = malloc(taille_message);

            strcpy(messageTmp, message);

            free(message);

            taille_message *= 2;

            message = malloc(taille_message);

            strcpy(message, messageTmp);
        }

        strcat(message, buff);

        if(finMessage(buff, message))
        {
            break;
        }
        else
        {
            for(i = 0; i < TAILLE_BUFF; i++)
            {
                buff[i] = 0; //Vide le buffer pour le prochain tour de boucle
            }
        }
    }

    gestionnaireCode(message);

    return message;
}

int envoieMessage(void* s, bool ssl, char * format, ...)
{
    va_list ap;

    va_start(ap, format);

    int taille = vsnprintf(NULL, 0, format, ap);

    va_end(ap);
    va_start(ap, format);

    char c[taille + 1];
    taille = vsnprintf(c, taille + 1, format, ap);

    va_end(ap);

    int envoye = 0, res;

    while(envoye < taille)
    {
        if(ssl)
        {
            res = SSL_write((SSL*)s, &c[envoye], taille - envoye);
        }
        else
        {
            res = send(*(SOCKET*)s, &c[envoye], taille - envoye, MSG_NOSIGNAL);
        }


        if(res == -1)
        {
            fprintf(stderr, "Probleme d'envoi des donnees %s\n", strerror(errno));
            return -1;
        }

        envoye += res;
    }

    return envoye;
}

//Recoie des données
int recoieDonneesNonSsl(int fd, SOCKET* s)
{
    char buff[TAILLE_BUFF];
    int res, res2;
    int nb_recu = 0;
    while(1)
    {
        res = recv(*s, buff, TAILLE_BUFF, 0);
        if (res < 0)
        {
            perror("Probleme à  la lecture du fichier");
            return -1;
        }
        if (res == 0)
        {
            close(*s);
            break;
        }

        nb_recu += res;
        //    fprintf(stderr, "Recu %d oct total %d oct\n", res, nb_recu);
        res2 = write(fd, buff, res);
        if (res != res2)
        {
            perror("Probleme à  l'ecriture du fichier");
            close(*s);
            return -1;
        }
    }
    return nb_recu;
}

int envoieDonneesNonSsl(SOCKET* s, FILE* fileInput)
{
    char * buffer;
    int res, res2;
    int envoye = 0;

    buffer = malloc(sizeof(char) * TAILLE_BUFF);

    int fdInput = fileno(fileInput);

    while(1)
    {
        res2 = read(fdInput, buffer, TAILLE_BUFF);

        res = send(*(SOCKET*)s, buffer, res2, MSG_NOSIGNAL);

        if(res == -1)
        {
            fprintf(stderr, "Probleme d'envoi des donnees %s\n", strerror(errno));
            fclose(fileInput);
            close(*s);
            return -1;
        }
        if(res2 != res)
        {
            fprintf(stderr, "Erreur d'écriture des données, read du fichier : %i, send : %i\n", res2, res);
        }

        envoye += res;

        if(res2 == 0)
        {
            break;
        }
    }

    close(*s);

    fclose(fileInput);

    return envoye;
}

int getPasvPort(void* s, bool ssl)
{
    char *message;
    message = malloc(sizeof(char) * 200);
    int i = 0;
    int port = 0;
    int a,b;

    envoieMessage(s, ssl, "PASV\r\n");

    message = recoieMessage(1, s, ssl);
    //calcul port
    strtok(message,",");
    for(i=0; i<4; i++)
    {
        a=atoi(strtok(NULL,","));
    }
    b=atoi(strtok(NULL,","));
    port=a*256+b;

    return port;
}


void SetStdinEcho(bool enable )
{
#ifdef WIN32
    HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE);
    DWORD mode;
    GetConsoleMode(hStdin, &mode);

    if( !enable )
        mode &= ~ENABLE_ECHO_INPUT;
    else
        mode |= ENABLE_ECHO_INPUT;

    SetConsoleMode(hStdin, mode );

#else
    struct termios tty;
    tcgetattr(STDIN_FILENO, &tty);
    if (!enable)
        tty.c_lflag &= ~ECHO;
    else
        tty.c_lflag |= ECHO;

    (void) tcsetattr(STDIN_FILENO, TCSANOW, &tty);
#endif
}

bool finMessage(char* buff, char* lastBuff)
{
    int i;

    if((buff[0] >= 49 && buff[0] <= 53) && (buff[1] >= 48 && buff[1] <= 57) && (buff[2] >= 48 && buff[2] <= 57) && buff[3] == ' ' && lastBuff[strlen(lastBuff) - 1] == '\n' && lastBuff[strlen(lastBuff) - 2] == '\r') //Si c'est un chiffre et un espace
    {
        return true;
    }


    for(i = 0; i < strlen(buff); i++)
    {
        if(buff[i] >= 48 && buff[i] <= 57) //Si c'est un chiffre
        {
            //Ce if c'est pour si y'a un retour à la ligne
            if(buff[i-2] == '\r' && buff[i-1] == '\n')
            {
                if(buff[i+3] == ' ') //Si espace après le code de retour = fin de la lecture de commande
                {
                    return true;
                }
            }
        }
    }
    return false;
}

bool isPasvCommand(char* command)
{
    char pasvCommand[NB_COMMAND_WITH_PASV][5] = {"LIST", "RETR", "LS", "STOR"}; //Ne pas oublier de changer le nombre dans ftpSocket.h
    int i;

    for(i = 0; i < NB_COMMAND_WITH_PASV; i++)
    {
        if(strcasecmp(command, pasvCommand[i]) == 0)
        {
            return true;
        }
    }

    return false;
}

void waitCmd(void* s, char *ip, bool ssl)
{
    pthread_t threadNoop, threadPrompt;

    argThread* argThread;
    argThread = malloc(sizeof(argThread));

    argThread->socket = s;
    argThread->ip = ip;
    argThread->ssl = ssl;

    pthread_mutex_init(&argThread->mutex, NULL);

    pthread_create(&threadNoop, NULL, (void*)&startNoop, argThread);
    pthread_create(&threadPrompt, NULL, (void*)&promptUtilisateur, argThread);

    pthread_join(threadPrompt, NULL);
    pthread_cancel(threadNoop);
}

void promptUtilisateur(argThread* arg)
{
    char* port;
    port = malloc(sizeof(char)* 15);

    SOCKET passiveSocket;

    int i;

    char * message;
    message = malloc(sizeof(char) * 512);

    char * downloadFolderName;
    downloadFolderName = malloc(sizeof(char) * LENGTH_CMD);
    strcpy(downloadFolderName, "download/");

    char* command;
    command = malloc(sizeof(char) * LENGTH_CMD);

    char* commandTmp;
    commandTmp = malloc(sizeof(char) * LENGTH_CMD);

    char* fileNameWithFolder;

    char* fileName;

    char* argument;

    while (1)
    {
        fileName = malloc(sizeof(char) * LENGTH_CMD);
        fileNameWithFolder = malloc(sizeof(char) * LENGTH_CMD);

        fprintf(stderr, "OK: Vous pouvez entrer une commande.\n");

        __fpurge(stdin);

        fgets(command, LENGTH_CMD, stdin);

        for(i = 0; i < strlen(command); i++) //Mettre en majuscule la commande entrée et éviter des bugs
        {
            if(command[i] == 32) //Si c'est un espace, on s'arrête
            {
                break;
            }
            if(command[i] >= 97 && command[i] <= 122)//Si c'est une lettre on la met en majuscule
            {
                command[i] -= 32;
            }
        }

        pthread_mutex_lock(&arg->mutex);

        strtok(command, "\n");

        strcpy(commandTmp, command);

        if(isPasvCommand(strtok(commandTmp, " ")))
        {

            sprintf(port, "%i", getPasvPort(arg->socket, arg->ssl)); //Convertit le int en char*

            passiveSocket = creeConnectSocketClient(arg->ip, port);

            if(strcmp(commandTmp, "LIST") == 0 || strcmp(commandTmp, "LS") == 0)
            {
                envoieMessage(arg->socket, arg->ssl, "LIST\r\n", command);

                recoieMessage(1, arg->socket, arg->ssl);

                recoieMessage(1, &passiveSocket, false);

                recoieMessage(1, arg->socket, arg->ssl);
            }

            if(strcmp(commandTmp, "STOR") == 0)
            {
                argument = malloc((sizeof(char) * (strlen(command)-strlen("STOR "))));

                for(i = 5; i <= strlen(command);i++)
                {
                    argument[i - 5] = command[i];
                    if(i == strlen(command))
                    {
                        argument[i] = '\0';
                    }
                }

                FILE *fileOutput = fopen(argument, "rb");

                if(fileOutput == NULL)
                {
                    fprintf(stderr, "Le fichier n'existe pas en local\n");
                    close(passiveSocket);
                }
                else
                {

                    envoieMessage(arg->socket, arg->ssl, "TYPE I\r\n");

                    recoieMessage(1, arg->socket, arg->ssl);

                    envoieMessage(arg->socket, arg->ssl, "%s\r\n", command);

                    if(envoieDonneesNonSsl(&passiveSocket, fileOutput) == -1)
                    {
                        fprintf(stderr, "Une erreur est survenue ligne 518, fermeture du programme");
                        break;
                    }

                    recoieMessage(1, arg->socket, arg->ssl);

                    recoieMessage(1, arg->socket, arg->ssl);

                    envoieMessage(arg->socket, arg->ssl, "TYPE A\r\n");

                    recoieMessage(1, arg->socket, arg->ssl);

                }

                free(argument);
            }

            if(strcmp(commandTmp, "RETR") == 0)
            {
                for(i = 5; i < LENGTH_CMD; i++)
                {
                    fileName[i - 5] = command[i];
                }

                envoieMessage(arg->socket, arg->ssl, "TYPE I\r\n");

                recoieMessage(1, arg->socket, arg->ssl);

                strcpy(fileNameWithFolder, downloadFolderName);

                FILE *fileOutput = fopen(strcat(fileNameWithFolder, fileName), "wb");
                if(fileOutput == NULL)
                {
                    if(mkdir("download", 0777) == -1)
                    {
                        fileOutput = fopen(fileName, "wb");
                    }
                    else
                    {
                        fileOutput = fopen(fileNameWithFolder, "wb");
                    }

                    if(fileOutput == NULL)
                    {
                        fprintf(stderr, "ERR: Le fichier en local n'a pas pu être ouvert\n");
                        exit(1);
                    }
                }

                int fdFile = fileno(fileOutput);

                envoieMessage(arg->socket, arg->ssl, "%s\r\n", command);

                message = recoieMessage(1, arg->socket, arg->ssl);

                if(!chercheCode(message, "550"))
                {
                    if(recoieDonneesNonSsl(fdFile, &passiveSocket) != -1)
                    {
                        recoieMessage(1, arg->socket, arg->ssl);

                        fprintf(stderr, "OK: Vous retrouverez votre fichier dans %s\n", fileNameWithFolder);
                    }
                    else
                    {
                        remove(fileNameWithFolder);

                        recoieMessage(1, arg->socket, arg->ssl);
                    }
                }
                else
                {
                    remove(fileNameWithFolder);
                }

                envoieMessage(arg->socket, arg->ssl, "TYPE A\r\n");

                recoieMessage(1, arg->socket, arg->ssl);

                free(fileName);
                free(fileNameWithFolder);
            }
        }
        else
        {
            if(strstr(command, "CD") != NULL)
            {
                argument = malloc((sizeof(char) * (strlen(command)-strlen("CD "))));

                for(i = 3; i <= strlen(command);i++)
                {
                    argument[i - 3] = command[i];
                    if(i == strlen(command))
                    {
                        argument[i] = '\0';
                    }
                }

                envoieMessage(arg->socket, arg->ssl, "CWD %s\r\n", argument);

                recoieMessage(1, arg->socket, arg->ssl);

                free(argument);
            }
            else
            {
                envoieMessage(arg->socket, arg->ssl, "%s\r\n", command);

                recoieMessage(1, arg->socket, arg->ssl);
            }

            if(strcmp(command, "QUIT") == 0)
            {
                break;
            }
        }
        pthread_mutex_unlock(&arg->mutex);
    }
}

void startNoop(argThread* arg)
{
    int fdPoubelle = open("/dev/null", O_WRONLY); //Redirection du file descriptor pour la réponse du NOOP

    while(1)
    {
        sleep(2);

        pthread_mutex_lock(&arg->mutex);

        envoieMessage(arg->socket, arg->ssl, "NOOP\r\n");

        recoieMessage(fdPoubelle, arg->socket, arg->ssl);

        pthread_mutex_unlock(&arg->mutex);
    }
}

bool chercheCode(char* message, char* code)
{
    char* finder;

    finder = strstr(message, code);

    if(finder != NULL)
    {
        return true;
    }

    return false;
}

void gestionnaireCode(char* message)
{
    if(chercheCode(message, "221"))
    {
        fprintf(stderr, "L'application FTP va se quitter. Réutilisez moi à souhait !");
    }
}
